package Analizadores;

import java.util.HashMap;

import Analizadores.AnalizadorLexico.CategoriaLexica;
import Analizadores.Estructuras.PilaTipos;
import Analizadores.Estructuras.TablaSimbolos;
import Analizadores.Estructuras.TokenList;
import Analizadores.Estructuras.Tipos.RegistroTS;
import Analizadores.Estructuras.Tipos.Token;
import Analizadores.Utilidades.Comprobaciones;
import Analizadores.Utilidades.Errores;
import Analizadores.Utilidades.GenCodigo;
import Analizadores.Utilidades.Usadas;
enum Tipos {real, entero, error};

//analizador sintactico y semantico
public class AnalizadorSintactico {


	private TokenList lista;//lista de token generados por el lexico
	private TablaSimbolos ts;//tabla de simbolos para las variables
	private PilaTipos pila;//pila de tipos para el control de tipado
	private Comprobaciones tablas;//Control de operaciones y tipos aceptados
	private Errores error;//clase para el mostrar el error por pantalla
	
	private String decId;//id del tipo que se esta declarando
	private String decTipo;//tipo de la variable que se esta declarando
	private Token tokenActual;//token actual que se esta procesando
	private int numInstruccion;//numero de instruccion en la que se encuentra el analizador
	private int numDir;// numero de direcciones ocupadas
	
	private GenCodigo genCodigo;//clase para la generacion de codigo
	
	public enum Tipos {real, entero, error};//tipos posibles en el semantico
	
	//constructora donde inicializamos todas las variables
	public AnalizadorSintactico(TokenList lista){
		this.lista = lista;
		numInstruccion=0;
		numDir = 0;
		HashMap<String, RegistroTS> t = new HashMap<String,RegistroTS>();
		ts = new TablaSimbolos(t);
		pila = new PilaTipos();
		tablas = new Comprobaciones();
		error = new Errores();
		genCodigo = new GenCodigo();
	}
	//getter del codigo
	public String getCodigo(){
		return genCodigo.getCodigo();
	}
	//asigna a token actual el token siguiente en la lista
	private void sigToken(){
		tokenActual = lista.getToken();
	}
	//funcion que procesa todos los token en orden de primero declaracion de variables y despues de acciones
	public void procesar(){
		SDec();//declaracion de variables
		genCodigo.genAmpersan();//escribimos un ampersan para que la maquina diferencia entre declaracion de variables y de acciones
		SAc();	// declaracion de acciones	
		System.out.println("FICHERO COMPILADO PERFECTAMENTE");
		
	}
	
	/******************************************************************************
	 * **************DECLARACION DE VARIABLES*************************************
	*******************************************************************************/
	private void SDec(){
		//SDec->Dec SDec
		//SDec -> *
		sigToken();//pedimos el siguiente token
		if (matchTipo()){//miramos si es de algun tipo de los posibles
			dec();//llamamos a la funcion para procesar la declaracion de variable
			SDec();	//volvemos para ver si hay mas declaraciones		
		}
	}
	//metodo para procesar la declaracion de la variable
	private void dec(){
		//Dec-> Tipo variable ;
		sigToken();
		if(!match(CategoriaLexica.CLID)){// miramos que es un id, sino es un error sintactico
			error.escribe("Se espera variable",numInstruccion,tokenActual);
		}
		Token t = tokenActual;
		sigToken();
		if (tokenActual.cat != CategoriaLexica.CLPUNTOCOMA){ //veemos que la declaracion acaba con punto y coma sino error
			error.escribe("Falta punto y coma",numInstruccion,tokenActual);
		}
		//SEMANTICO
		Tipos tip = null;
		//pasamos a nuestro tipo el tipo que esta escrito en el codigo
		if(decTipo.equals("int")) tip = Tipos.entero;
		else if(decTipo.equals("real")) tip = Tipos.real;
		RegistroTS reg = new RegistroTS(tip,numDir);
		//añadimos la variable con su informacion a la t. de simbolos sino se puede, llamamos a error 
		if(ts.anadeId(decId, reg)==null){
			error.escribe("Variable ya existente",numInstruccion,t);
		}
		genCodigo.genDecVar(decTipo, decId, numDir);//generamos el codigo objeto
		numDir++;//sumamos 1 al numero de direcciones
		numInstruccion++;//sumamos una instruccion
	}
	//mira si es de algun tipo aceptado por el lenguaje
	private boolean matchTipo(){
		decTipo = tokenActual.lex;
		return (tokenActual.cat == CategoriaLexica.CLTINT || tokenActual.cat == CategoriaLexica.CLTREAL);
	}
	//mira si es de la categoria ID
	private boolean match(CategoriaLexica c){
		decId = tokenActual.lex;
		return (tokenActual.cat == c);
	}
	
	/******************************************************************************
	 * **************DECLARACION DE ACCIONES**************************************
	*******************************************************************************/
	
	
	private void SAc(){
		//SAc -> Ac RSA
		//SAc -> Ac
		//comprobamos que hay una primera accion, sino publicamos el error
		if (tokenActual.cat == CategoriaLexica.CLEOF){
			error.escribe("No hay primera accion",numInstruccion,tokenActual);
		}
		Ac();//procesamos la accion y comprobamos que termina en ; sino, error
		if(tokenActual.cat == CategoriaLexica.CLPUNTOCOMA){
			sigToken();
			numInstruccion++;//sumamos una instruccion
			RSA();//vemos si hay mas acciones
		}
		else{
			error.escribe("Falta punto y coma",numInstruccion,tokenActual);
		}
		
			
	}
	//metodo para procesar acciones
	private void RSA(){
		if (tokenActual.cat != CategoriaLexica.CLEOF){//miramos si no se ha acabado el codigo
			Ac();//procesamos la accion
			if(tokenActual.cat == CategoriaLexica.CLPUNTOCOMA){//vemos que termina correctamente la instruccion
				pila.vaciaPila();//vaciamos la pila de tipos, para la siguiente instruccion
				sigToken();
				numInstruccion++;//sumamos una instruccion
				RSA();//procesamos las siguientes acciones
			}
			else{
				error.escribe("Falta punto y coma",numInstruccion,tokenActual);
			}
		}
	}
	private void Ac(){
		//vemos que la instruccion comience de manera correcta, con in, con out, o con una asignacion
		if((tokenActual.cat == CategoriaLexica.CLID)&&(lista.mirarToken(1).cat != CategoriaLexica.CLASIG))
			error.escribe("Mal comienzo de instruccion",numInstruccion,lista.mirarToken(1));
		if((tokenActual.cat != CategoriaLexica.CLID)&& (tokenActual.cat != CategoriaLexica.CLIN)&&
			(tokenActual.cat != CategoriaLexica.CLOUT))
				error.escribe("Mal comienzo de instruccion",numInstruccion,tokenActual);
		Exp();//procesamos la expresion
		
		
	}
	//expresion de prioridad 0 ( in y out)
	private void Exp(){
		//Exp -> in variable
		//Exp -> out Exp
		//Exp -> Exp1
		if(match(CategoriaLexica.CLIN)){
			sigToken();
			//comprobamos que a continuacion viene un id, sino es error
			if (match(CategoriaLexica.CLID)){
				//comprobamos que exite el id, sino, error
				if (!ts.existeId(tokenActual.lex))
					error.escribe("Variable no existente", numInstruccion, tokenActual);
				System.out.println("in");
				genCodigo.generaIn(ts.dirDe(tokenActual.lex));//generamos el codigo con la direccion del id
				pila.apila(dameTipo());//apilamos el tipo del la variable que pedimos por pantalla
				genCodigo.apilaDir(ts.dirDe(tokenActual.lex));//generamos el codigo de la variable que hemos leido
				sigToken();
			}
			else{
				error.escribe("Se espera variable",numInstruccion,tokenActual);
			}
			
		}
		else if (match(CategoriaLexica.CLOUT)){
			System.out.println("out");
			int direccion =-1;//inicializamos la direccion a -1, por si el out no es sobre un id.
			if(lista.mirarToken(1).cat == CategoriaLexica.CLID) { //comprobamos si es un id
				if (!ts.existeId(lista.mirarToken(1).lex))//vemos que exista el id, sino error
					error.escribe("Variable no existente", numInstruccion, tokenActual);
				direccion = ts.dirDe(lista.mirarToken(1).lex);	//guardamos la direccion del id
			}	
			sigToken();
			Exp();//procesamos lo que viene acontinuacion
			genCodigo.generaOut(direccion);//generamos el out, despues de procesar toda la expresion
			
		}
		
		else{
			Exp1();//Pasamos de prioridad
		}
	}
	private void Exp1(){
		//Exp -> id RExp1
		//Exp -> Exp2
		if((tokenActual.cat == CategoriaLexica.CLID)&&(lista.mirarToken(1).cat== CategoriaLexica.CLASIG)){
			if (!ts.existeId(tokenActual.lex))//vemos que la variable existe
				error.escribe("Variable no existente", numInstruccion, tokenActual);
			int dir = ts.dirDe(tokenActual.lex);//guardamos la direccion
			Tipos tipo = dameTipo();	//guardamos el tipo
			Usadas usadas = new Usadas();//creamos la estructura usadas para poder ver que no se duplica la variable
			usadas.inserta(dir);//insertamos la variable leida
			System.out.println("Asig");
			sigToken();
			Token tOp = tokenActual;//guardamos el token actual, para el error
			CategoriaLexica operacion = tokenActual.cat;//guardamos la operacion para comprobar el tipado
			RExp1(usadas);//procesamos lo que sigue de expresion pasando la variable de variables usadas
			tipo = tablas.daResultado(operacion, tipo, pila.desapila());//comprobamos el tipado
			if(tipo == Tipos.error)  error.escribe("Fallo semantico",numInstruccion,tOp);
			pila.apila(tipo);//apila el tipo
			genCodigo.desapilaDir(dir);//genera el codigo para guardar el valor en la variable
		}
		else
			Exp2();
	}
	private void RExp1(Usadas usadas){
		//RExp1 -> RExp2
		//RExp1 -> RExp3
		//RExp1 -> RExp4
		//RExp1 -> = RestoExp1
		//RExp1 -> *
		if(esOpExp2())
			RExp2();
		else if(esOpExp3())
			RExp3();
		else if(esOpExp4())
			RExp4();
		else if(tokenActual.cat == CategoriaLexica.CLASIG){
			sigToken();
			RestoExp1(usadas);
		}
			
	}
	private void RestoExp1(Usadas usadas){
		//RestoExp1 -> id RExp1
		//RestoExp -> Exp2
		if (tokenActual.cat == CategoriaLexica.CLID){
			if (!ts.existeId(tokenActual.lex))//comprobamos que existe la variable
				error.escribe("Variable no existente", numInstruccion, tokenActual);
			System.out.println("ID RestoExp1");
			int direccion = ts.dirDe(tokenActual.lex);//guardamos la direccion
			Tipos tipo = dameTipo();//guardamos el tipo
			Token tOp = tokenActual;//guardamos el token por si se genera un error
			genCodigo.apilaDir(direccion);//generamos el codigo para apilar el valor
			pila.apila(tipo);//apilamos el tipo
			sigToken();
			CategoriaLexica operacion = null;
			if(tokenActual.cat == CategoriaLexica.CLASIG) {//vemos si hay asignacion o no
				operacion = tokenActual.cat;				
			}
			if (((tokenActual.cat == CategoriaLexica.CLPUNTOCOMA)||(tokenActual.cat == CategoriaLexica.CLASIG))
				&&(!usadas.inserta(direccion)))//comprobamos que la variable no ha sido usada antes si va a ver mas asignaciones
						error.escribe("Variable duplicada en la asignacion",numInstruccion,tOp);
			RExp1(usadas);//procesamos lo que queda de expresion
			if (operacion != null){
				tipo = tablas.daResultado(CategoriaLexica.CLASIG, tipo, pila.desapila());//comprobamos el tipado
				if(tipo == Tipos.error)  error.escribe("Fallo semantico",numInstruccion,tOp);	
				pila.apila(tipo);//apilamos el tipo
				genCodigo.desapilaDir(direccion);//escribimos el codigo para guardar en la variable
				genCodigo.apilaDir(direccion);//escribimos el codigo para apilar el valor de la variable
			}
		}
		else
			Exp2();//procesamos la expresion
	}
	private void Exp2(){
		//Exp2 -> Exp3 RExp2
		Exp3();
		RExp2();
		
	}
	
	private void RExp2(){
		//RExp2 -> opExp2 Exp3 RExp2
		if (esOpExp2()){
			CategoriaLexica operacion = tokenActual.cat;//guardamos el tipo de operacion
			String op = tokenActual.lex;//guardamos el lexema
			Token tOp = tokenActual;//guardamos el token actual por si hay fallo
			sigToken();
			System.out.println("RE exp2");
			Exp3();//seguimos procesando la expresion
			RExp2();
			Tipos tipo = tablas.daResultado(operacion, pila.desapila(), pila.desapila());//comprobamos el tipado
			if(tipo == Tipos.error)  error.escribe("Fallo semantico",numInstruccion,tOp);
			pila.apila(tipo);//apila el tipo
			genCodigo.generaOp(operacion,op);//genera el codigo de la operacion correspondiente
		}
	}
	
	private void Exp3(){
		//Exp3 -> Exp4 RExp3
		Exp4();
		RExp3();
	}
	
	private void RExp3(){
		//RExp3 -> opExp3 Exp4 RExp3
		if(esOpExp3()){
				String op= tokenActual.lex;//guardamos el lexema
				CategoriaLexica operacion = tokenActual.cat;//guardamos el tipo de operacion
				Token tOp = tokenActual;//guardamos el token actual por si hay error
				sigToken();
				System.out.println("RE exp3");
				Exp4();//procesamos lo que queda de expresion
				RExp3();
				Tipos tipo = tablas.daResultado(operacion, pila.desapila(), pila.desapila());//comprobamos el tipado
				if(tipo == Tipos.error)  error.escribe("Fallo semantico",numInstruccion,tOp);
				pila.apila(tipo);//apila el tipo
				genCodigo.generaOp(operacion,op);//genera el codigo de la operacion correspondiente
		}
		
	}
	
	private void Exp4(){
		//Exp4 -> Exp5 RExp4
		Exp5();
		RExp4();
	}
	
	private void RExp4(){
		//RExp4 -> opExp4 Exp5 RExp4
		if(esOpExp4()){
				String op= tokenActual.lex;//guardamos el lexema
				CategoriaLexica operacion = tokenActual.cat;//guardamos el tipo de operacion
				System.out.println("RE exp4");
				Token tOp = tokenActual;//guardamos el token actual por si ocurre un error
				sigToken();
				Exp5();//seguimos procesando la expresion restante
				RExp4();
				Tipos tipo = tablas.daResultado(operacion, pila.desapila(), pila.desapila());
				if(tipo == Tipos.error)  error.escribe("Fallo semantico",numInstruccion,tOp);
				pila.apila(tipo);// apila el tipo
				genCodigo.generaOp(operacion,op);//genera el codigo de la operacion correspondiente
		}
		
	}
	private void Exp5(){
		//Exp5 -> - Exp5
		//Exp5 -> ! Exp5
		//Exp5 -> (int) Exp6
		//Exp5 -> (real) Exp6
		//Exp5 -> Exp6
		if(tokenActual.cat == CategoriaLexica.CLRESTA || tokenActual.cat == CategoriaLexica.CLNEG){
			System.out.println("Distinto o resta exp5");
			String op = tokenActual.lex;//guardamos el lexema
			CategoriaLexica operacion = tokenActual.cat;//guardamos el tipo de operacion
			Token tOp = tokenActual;//guardamos el token actual por si hay error
			sigToken();
			Exp5();//procesamos lo que queda de expresion
			Tipos tipo = tablas.daResultado(operacion, pila.desapila());//comprobamos el tipado
			if(tipo == Tipos.error)  error.escribe("Fallo semantico",numInstruccion,tOp);
			pila.apila(tipo);//apilamos el tipo resultante
			genCodigo.generaOpUnario(op);//generamos el codigo para la operacion
		}
		else if(tokenActual.cat == CategoriaLexica.CLCASTREAL || tokenActual.cat == CategoriaLexica.CLCASTENT){
			String op = tokenActual.lex;//guardamos el lexema
			CategoriaLexica operacion = tokenActual.cat;//guardamos el tipo de operacion
			Token tOp = tokenActual;//guardamos el token actual por si hay error
			sigToken();
			Exp6();// procesamos la expresion restante
			Tipos tipo = tablas.daResultado(operacion, pila.desapila());//comprobamos el tipado
			if(tipo == Tipos.error)  error.escribe("Fallo semantico",numInstruccion,tOp);
			pila.apila(tipo);//apilamos el tipo resultante
			genCodigo.generaOpCast(op);//generamos el codigo de la operacion correspondiente
		}
		else{
			Exp6();
		}
		
	}
	private void Exp6(){
		if(tokenActual.cat == CategoriaLexica.CLENT){
			System.out.println("Ient exp6");
			genCodigo.apila(tokenActual.lex);//generamos el codigo de apilar un valor
			pila.apila(Tipos.entero);//apilamos el tipo
		}
		else if(tokenActual.cat == CategoriaLexica.CLREAL){
			System.out.println("real exp6");
			genCodigo.apila(tokenActual.lex);//generamos el codigo de apilar un valor
			pila.apila(Tipos.real);//apilamos el tipo
		}
		else if(tokenActual.cat == CategoriaLexica.CLID){
			if (!ts.existeId(tokenActual.lex))//comprobamos que exite la variable
				error.escribe("Variable no existente", numInstruccion, tokenActual);
			System.out.println("ID exp6");
			genCodigo.apilaDir(ts.dirDe(tokenActual.lex));//generamos el codigo de apilar una direccion
			pila.apila(dameTipo());//apilamos el tipo
		}
		else if(tokenActual.cat == CategoriaLexica.CLPA){
			System.out.println("Parentesis abierto");
			sigToken();
			Exp();//procesamos lo que resta de expresion
			if(tokenActual.cat == CategoriaLexica.CLPC){//comprobamos que se cierra el parentesis, sino, error
				System.out.println("Parentesis cerrado");
			}
			else{
				error.escribe("No cierre de parentesis",numInstruccion,tokenActual);
			}
		}
		else{
			error.escribe("Caracter no esperado",numInstruccion,tokenActual);
		}
		if(tokenActual.cat != CategoriaLexica.CLEOF)			
			sigToken();
	}
	//getter del tipo de una variable
	private Tipos dameTipo(){
		return ts.tipoDe(tokenActual.lex);
	}
	//comprueba si la operacion es de tipo de la exp2
	private boolean esOpExp2(){
		if (tokenActual.cat == CategoriaLexica.CLMENOR || tokenActual.cat == CategoriaLexica.CLMAYOR ||
				tokenActual.cat == CategoriaLexica.CLMENORIG || tokenActual.cat == CategoriaLexica.CLMAYORIG
				|| tokenActual.cat == CategoriaLexica.CLIGUAL || tokenActual.cat == CategoriaLexica.CLDISTINTO)
						return true;
		return false;
	}
	//comprueba si la operacion es de tipo de la exp3
	private boolean esOpExp3(){
		if(tokenActual.cat == CategoriaLexica.CLOR 
			|| tokenActual.cat == CategoriaLexica.CLSUMA 
		    || tokenActual.cat == CategoriaLexica.CLRESTA)
					return true;
		return false;
	}
	//comprueba si la operacion es de tipo de la exp4
	private boolean esOpExp4(){
		if(tokenActual.cat == CategoriaLexica.CLMUL
			|| tokenActual.cat == CategoriaLexica.CLDIV
			|| tokenActual.cat == CategoriaLexica.CLMOD
			|| tokenActual.cat == CategoriaLexica.CLAND)
					return true;
		return false;
	}

}